﻿namespace Summoner
{
    using UnityEngine;
    using System;
    using System.Collections.Generic;
    using Object = UnityEngine.Object;

    public class GameObjectPoolCtr : IDisposable
    {
        /// <summary>
        /// tell out pool manager is enabled or not
        /// </summary>
        private bool _poolManagerEnabled;
        /// <summary>
        /// Prefab to pool
        /// </summary>
        private Dictionary<string, GameObjectPool> _name2pool;
        /// <summary>
        /// name to prefab
        /// </summary>
        private Dictionary<string, GameObject> _name2prefab;
        /// <summary>
        /// name to limitation
        /// </summary>
        private Dictionary<string, int> _name2limit;
        /// <summary>
        /// root holder for pooled objects
        /// </summary>
        private GameObject _poolroot = null;

        public GameObjectPoolCtr()
        {
            this._poolManagerEnabled = true;
            this._name2pool = new Dictionary<string, GameObjectPool>();
            this._name2prefab = new Dictionary<string, GameObject>();
            this._name2limit = new Dictionary<string, int>();
            this._poolroot = new GameObject("Pool-" + this.GetFormNumber());
        }
        public string GetFormNumber()
        {
            byte[] buffer = Guid.NewGuid().ToByteArray();
            var FormNumber = BitConverter.ToUInt32(buffer, 0) ^ BitConverter.ToUInt32(buffer, 4) ^ BitConverter.ToUInt32(buffer, 8) ^ BitConverter.ToUInt32(buffer, 12);
            return FormNumber.ToString("X");
        }

        private string GetObjectName(string name)
        {
            int index = name.IndexOf("(Clone)");
            if (index > 0)
            {
                return name.Substring(0, index);
            }
            return name;
        }

        /// <summary>
        /// Pre pooled object, set prepooled count and set pooled limitation
        /// </summary>
        /// <param name="prefab">pre pool object</param>
        /// <param name="count">pre pool count</param>
        /// <param name="limit">pooled count limitation</param>
        public void PrePooled(GameObject prefab, int count = 1, int limit = 0)
        {
            if ((prefab != null) && this._poolManagerEnabled)
            {
                GameObjectPool pool;
                if (this._poolroot != null) { prefab.transform.SetParent(this._poolroot.transform); }
                string name = this.GetObjectName(prefab.name);
                if (!this._name2prefab.ContainsKey(name))
                {
                    this._name2prefab[name] = prefab;
                    this._name2limit[name] = limit;
                }
                else
                {
                    Object.Destroy(prefab);
                }
                if (!this._name2pool.ContainsKey(name))
                {
                    pool = this.CreateObjectPool(prefab);
                    this._name2pool[name] = pool;
                }
                else
                {
                    pool = this._name2pool[name];
                }
                for (int i = 0; i < count; i++)
                {
                    GameObject obj = pool.Instantiate(true);
                    if (obj != null)
                    {
                        if (this._poolroot != null) { obj.transform.SetParent(this._poolroot.transform); }
                    }
                }
            }
        }

        /// <summary>
        /// Create pool
        /// </summary>
        /// <param name="prefab"></param>
        /// <returns></returns>
        private GameObjectPool CreateObjectPool(GameObject prefab)
        {
            GameObjectPool pool = new GameObjectPool();
            pool.Init(prefab);
            return pool;
        }

        /// <summary>
        /// Create pooled prefab
        /// </summary>
        /// <param name="prefab"></param>
        /// <param name="position"></param>
        /// <param name="rotation"></param>
        /// <returns></returns>
        private GameObject CreatePooledObject(GameObject prefab)
        {
            if (prefab == null)
            {
                Debug.LogError("prefab is null");
                return null;
            }
            GameObjectPool pool;
            string name = this.GetObjectName(prefab.name);
            if (!this._name2pool.ContainsKey(name))
            {
                //Debug.Log("not pooled this ----------------");
                pool = this.CreateObjectPool(prefab);
                this._name2pool[name] = pool;
            }
            else
            {
                //Debug.Log("use pool object -----------------");
                pool = this._name2pool[name];
            }
            GameObject obj = pool.Instantiate(false);
            if (obj != null)
            {
                obj.SetActive(true);
            }
            int index = obj.name.IndexOf("(");
            if (index > 0) { obj.name = obj.name.Substring(0, index); }
            return obj;
        }

        public GameObject RentPooledObject(string name)
        {
            if (this._poolManagerEnabled && this._name2prefab.ContainsKey(name))
            {
                return CreatePooledObject(this._name2prefab[name]);
            }
            else
            {
                Debug.LogError("there is no " + name + " object in the pool");
                return null;
            }
        }

        /// <summary>
        /// Delete all
        /// </summary>
        public void DeleteAllPooledObjects()
        {
            foreach(KeyValuePair<string, GameObjectPool> kvp in this._name2pool)
            {
                kvp.Value.End();
                kvp.Value.Dispose();
            }
            this._name2pool.Clear();
            this._name2prefab.Clear();
            this._name2limit.Clear();
        }

        /// <summary>
        /// return object to pool
        /// </summary>
        /// <param name="obj"></param>
        public void ReturnToPool(GameObject obj)
        {
            if (obj != null)
            {
                string name = this.GetObjectName(obj.name);
                if (!this._poolManagerEnabled)
                {
                    UnityEngine.Object.Destroy(obj);
                }
                else if (this._name2pool.ContainsKey(name))
                {
                    obj.transform.SetParent(null);
                    if (this._poolroot != null) { obj.transform.SetParent(this._poolroot.transform); }
                    this._name2pool[name].Recycle(obj);
                    if (this._name2limit[name] != 0 && this._name2limit[name] < this.GetPoolCount(obj))
                    {
                        this._name2pool[name].RemoveOne();
                    }
                }
                else
                {
                    UnityEngine.Object.Destroy(obj);
                }
            }
        }

        private void RemoveFromPool(string name)
        {
            if (this._name2pool.ContainsKey(name))
            {
                GameObjectPool pool = this._name2pool[name];
                pool.Dispose();
                this._name2pool.Remove(name);
                this._name2prefab.Remove(name);
                this._name2limit.Remove(name);
            }
        }

        /// <summary>
        /// delete object pool
        /// </summary>
        /// <param name="prefab"></param>
        public void DeletePooledObject(GameObject prefab)
        {
            if (prefab != null)
            {
                if (!this._poolManagerEnabled)
                {
                    Object.Destroy(prefab);
                }
                else
                {
                    string name = this.GetObjectName(prefab.name);
                    if (this._name2pool.ContainsKey(name))
                    {
                        GameObjectPool pool = this._name2pool[name];
                        prefab.transform.SetParent(null);
                        pool.Recycle(prefab);
                        this.RemoveFromPool(name);
                    }
                }
            }
        }

        public void DeletePooledObject(string name)
        {
            if (this._name2pool.ContainsKey(name))
            {
                this.RemoveFromPool(name);
            }
        }

        /// <summary>
        /// Disable the pool manager
        /// </summary>
        public void DisablePoolManager()
        {
            this._poolManagerEnabled = false;
        }

        /// <summary>
        /// Enable the pool manager
        /// </summary>
        public void EnablePoolManager()
        {
            this._poolManagerEnabled = true;
        }
        /// <summary>
        /// Get pooled objects count
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public int GetPoolCount(string name)
        {
            if (this._name2pool.ContainsKey(name))
            {
                return this._name2pool[name].Count;
            }
            return 0;
        }

        public int GetPoolCount(GameObject prefab)
        {
            return GetPoolCount(this.GetObjectName(prefab.name));
        }

        public bool AlreadyPooled(string name)
        {
            return this._name2prefab.ContainsKey(this.GetObjectName(name));
        }

        public void End()
        {
            this._poolManagerEnabled = false;
            this.DeleteAllPooledObjects();
            if (this._poolroot != null) { Object.Destroy(this._poolroot); }
        }

        #region IDisposable
        private bool disposed = false; // To detect redundant calls

        // Public implementation of Dispose pattern callable by consumers.
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // Protected implementation of Dispose pattern.
        protected virtual void Dispose(bool disposing)
        {
            if (disposed) { return; }
            if (disposing)
            {
                // Free any other managed objects here.
                this.End();
            }
            // Free any unmanaged objects here.
            disposed = true;
        }
        #endregion

        /// <summary>
        /// Get poolManager status
        /// </summary>
        public bool poolManagerEnabled { get { return _poolManagerEnabled; } }
    }
}